home *** CD-ROM | disk | FTP | other *** search
/ FishMarket 1.0 / FishMarket v1.0.iso / fishies / 301-325 / disk_319 / cnewssrc / cnews.orig.lzh / expire / expire.c < prev    next >
C/C++ Source or Header  |  1989-06-27  |  27KB  |  1,273 lines

  1. /*
  2.  * expire - expire old news
  3.  *
  4.  * One modest flaw:  links are not preserved in archived copies, i.e. you
  5.  * get multiple copies of multiply-posted articles.  Since link preservation
  6.  * is arbitrarily hard when control files get complex, to hell with it.
  7.  */
  8.  
  9. #include <stdio.h>
  10. #include <ctype.h>
  11. #include <string.h>
  12. #include <errno.h>
  13. #include <time.h>
  14. #include <signal.h>
  15. #include <sys/types.h>
  16. #include <sys/timeb.h>
  17. #include <sys/stat.h>
  18. #include "libc.h"
  19. #include "news.h"
  20. #include "config.h"
  21. #include "fgetmfs.h"
  22.  
  23. #ifndef EPOCH
  24. #define    EPOCH    ((time_t)0)
  25. #endif
  26.  
  27.  
  28. /* structure for dbm */
  29. typedef struct {
  30.     char *dptr;
  31.     int dsize;
  32. } datum;
  33.  
  34. #define    DAY    ((double)24*60*60)
  35.  
  36. /* structure for expiry-control records */
  37. struct ctl {
  38.     struct ctl *next;
  39.     char *groups;        /* newsgroups */
  40.     int ismod;        /* moderated? */
  41. #        define    UNMOD    'u'
  42. #        define    MOD    'm'
  43. #        define    EITHER    'x'
  44.     time_t retain;        /* earliest arrival date not expired */
  45.     time_t normal;        /* earliest not expired in default case */
  46.     time_t purge;        /* latest arrival date always expired */
  47.     char *dir;        /* Archive dir or NULL. */
  48. };
  49.  
  50. /* header for internal form of control file */
  51. struct ctl *ctls = NULL;
  52. struct ctl *lastctl = NULL;
  53.  
  54. /*
  55.  * Headers for lists by newsgroup, derived (mostly) from active file.
  56.  * Hashing is by length of newsgroup name; this is quick and works well,
  57.  * and there is no simple variation that does much better.
  58.  */
  59. #define    NHASH    80
  60. struct ctl *ngs[NHASH] = { NULL };
  61.  
  62. struct ctl *holdover = NULL;    /* "/expired/" control record */
  63. struct ctl *bounds = NULL;    /* "/bounds/" control record */
  64.  
  65. int debug = 0;            /* for inews routines */
  66. int expdebug = 0;        /* expire debugging */
  67.  
  68. int printexpiring = 0;        /* print info line for expiring articles? */
  69. char *defarch = NULL;        /* default archive dir */
  70. int spacetight = 0;        /* error-recovery actions remove evidence? */
  71.  
  72. char *subsep = "~";        /* subfield separator in middle field */
  73. int checkonly = 0;        /* check control information only */
  74. int testing = 0;        /* testing only, leave articles alone */
  75. int leaders = 0;        /* only first link ("leader") is hard link */
  76. int verbose = 0;        /* report statistics */
  77.  
  78. long nkept = 0;            /* count of articles not expired */
  79. long ngone = 0;            /* count of articles removed (no links left) */
  80. long nresid = 0;        /* count of residual entries kept */
  81. long narched = 0;        /* count of links archived */
  82. long njunked = 0;        /* count of links just removed */
  83. long nmissing = 0;        /* count of links missing at cp/rm time */
  84.  
  85. char dont[] = "don't";        /* magic cookie for whereexpire() return */
  86.  
  87. time_t now;
  88. struct timeb ftnow;        /* ftime() result for getdate() */
  89. #define    NODATE    ((time_t)(-1))    /* time_t value indicating date not given */
  90.  
  91. char subject[200] = "";        /* Subject line for -p, minus header */
  92.  
  93. /* Buffer etc. for readline and friends. */
  94. char rlbuf[BUFSIZ];
  95. int rlnleft = 0;
  96. char *rest;
  97. int nlocked = 0;        /* has readline() locked the news system? */
  98.  
  99. /*
  100.  * Archive-copying buffer.
  101.  * 8KB buffer is large enough to take most articles at one gulp,
  102.  * and also large enough for virtual certainty of getting the
  103.  * Subject: line in the first bufferload.
  104.  */
  105. char abuf[8*1024];
  106.  
  107. char *progname;
  108.  
  109. extern int errno;
  110. extern long atol();
  111. extern double atof();
  112. extern char *malloc();
  113. extern struct tm *gmtime();
  114. extern time_t time();
  115.  
  116. extern time_t getdate();
  117.  
  118. /* forwards */
  119. FILE *eufopen();
  120. void eufclose();
  121. void euclose();
  122. char *whereexpire();
  123. time_t back();
  124. void checkdir();
  125. void fail();
  126. void control();
  127. void prime();
  128. void doit();
  129. void cd();
  130. char *doline();
  131. time_t readdate();
  132. char *doarticle();
  133. void warning();
  134. void printstuff();
  135. void expire();
  136. char *readline();
  137. void mkparents();
  138. void getsubj();
  139. void refill();
  140. void printlists();
  141. void pctl();
  142. void fillin();
  143. void ctlline();
  144. char *strvsave();
  145.  
  146. /*
  147.  - main - parse arguments and handle options
  148.  */
  149. main(argc, argv)
  150. int argc;
  151. char *argv[];
  152. {
  153.     register int c;
  154.     register int errflg = 0;
  155.     register FILE *cf;
  156.     extern int optind;
  157.     extern char *optarg;
  158.  
  159.     progname = argv[0];
  160.     now = time((time_t *)NULL);
  161.     ftime(&ftnow);
  162.  
  163.     while ((c = getopt(argc, argv, "pa:sF:cn:tlvd")) != EOF)
  164.         switch (c) {
  165.         case 'p':    /* print info line for archived articles */
  166.             printexpiring = 1;
  167.             break;
  168.         case 'a':    /* archive in this directory */
  169.             defarch = optarg;
  170.             break;
  171.         case 's':    /* maximize space during error recovery */
  172.             spacetight = 1;
  173.             break;
  174.         case 'F':    /* subfield separator in middle field */
  175.             subsep = optarg;
  176.             break;
  177.         case 'c':    /* check control-file format only */
  178.             checkonly = 1;
  179.             break;
  180.         case 'n':    /* set value of "now" for testing */
  181.             now = atol(optarg);
  182.             break;
  183.         case 't':    /* testing, do not mess with articles */
  184.             testing = 1;
  185.             break;
  186.         case 'l':    /* leaders */
  187.             leaders = 1;
  188.             break;
  189.         case 'v':    /* verbose -- report some statistics */
  190.             verbose = 1;
  191.             break;
  192.         case 'd':    /* debug */
  193.             expdebug = 1;
  194.             break;
  195.         case '?':
  196.         default:
  197.             errflg++;
  198.             break;
  199.         }
  200.     if (errflg || optind < argc-1) {
  201.         fprintf(stderr, "Usage: %s [-p] [-s] [-c] [-a archdir] [ctlfile]\n",
  202.                                 progname);
  203.         exit(2);
  204.     }
  205.     if (expdebug)
  206.         setbuf(stderr, (char *)NULL);
  207.  
  208.     if (optind < argc) {
  209.         cf = eufopen(argv[optind], "r");
  210.         control(cf);
  211.         (void) fclose(cf);
  212.     } else
  213.         control(stdin);
  214.     prime(ctlfile("active"));
  215.  
  216.     if (expdebug)
  217.         printlists();
  218.     if (defarch != NULL)
  219.         checkdir(defarch);
  220.     if (checkonly)
  221.         exit(0);
  222.  
  223.  
  224.     (void) umask(newsumask());
  225.     doit();            /* side effect: newslock() */
  226.     newsunlock();
  227.  
  228.     if (verbose) {
  229.         fprintf(stderr, "%ld kept, %ld expired\n", nkept, ngone);
  230.         fprintf(stderr, "%ld residual lines\n", nresid);
  231.         fprintf(stderr, "%ld links archived, %ld junked, %ld missing\n",
  232.                         narched, njunked, nmissing);
  233.     }
  234.     exit(0);
  235. }
  236.  
  237. /*
  238.  - control - pick up a control file
  239.  */
  240. void
  241. control(f)
  242. register FILE *f;
  243. {
  244.     char line[200];        /* long enough for any sane line */
  245.     register char *p;
  246.     void ctlline();
  247.  
  248.     while (fgets(line, sizeof(line), f) != NULL) {
  249.         p = &line[strlen(line) - 1];
  250.         if (*p != '\n')
  251.             fail("control line `%.30s...' too long", line);
  252.         *p = '\0';
  253.         if (line[0] != '#' && line[0] != '\0')
  254.             ctlline(line);
  255.     }
  256. }
  257.  
  258. /*
  259.  - ctlline - process one control-file line
  260.  */
  261. void
  262. ctlline(ctl)
  263. char *ctl;
  264. {
  265.     register struct ctl *ct;
  266.     char *field[4];
  267.     char datebuf[50];
  268.     char *dates[3];
  269.     register int nf;
  270.     int ndates;
  271.  
  272.     errno = 0;
  273.     nf = split(ctl, field, 4, "\t ");
  274.     if (nf != 4)
  275.         fail("control line `%.20s...' hasn't got 4 fields", ctl);
  276.  
  277.     errno = 0;
  278.     ct = (struct ctl *)malloc(sizeof(struct ctl));
  279.     if (ct == NULL)
  280.         fail("out of memory for control list", "");
  281.  
  282.  
  283.     ct->groups = strsave(field[0]);
  284.     if (STREQ(field[1], "m"))
  285.         ct->ismod = MOD;
  286.     else if (STREQ(field[1], "u"))
  287.         ct->ismod = UNMOD;
  288.     else if (STREQ(field[1], "x"))
  289.         ct->ismod = EITHER;
  290.     else
  291.         fail("strange mod field `%s' in control file", field[1]);
  292.  
  293.     if (strlen(field[2]) > sizeof(datebuf)-1)
  294.         fail("date specification `%s' too long", field[2]);
  295.     (void) strcpy(datebuf, field[2]);
  296.     ndates = split(datebuf, dates, 3, "-");
  297.     switch (ndates) {
  298.     case 3:
  299.         ct->retain = back(atof(dates[0]));
  300.         ct->normal = back(atof(dates[1]));
  301.         ct->purge = back(atof(dates[2]));
  302.         break;
  303.     case 2:
  304.         ct->retain = (bounds != NULL) ? bounds->retain : back(0.0);
  305.         ct->normal = back(atof(dates[0]));
  306.         ct->purge = back(atof(dates[1]));
  307.         break;
  308.     case 1:
  309.         ct->retain = (bounds != NULL) ? bounds->retain : back(0.0);
  310.         ct->normal = back(atof(dates[0]));
  311.         ct->purge = (bounds != NULL) ? bounds->purge : EPOCH;
  312.         break;
  313.     default:
  314.         fail("date processing foulup in `%s'", field[1]);
  315.         /* NOTREACHED */
  316.         break;
  317.     }
  318.     if (ct->retain < ct->normal || ct->normal < ct->purge)
  319.         fail("preposterous dates: `%s'", field[2]);
  320.  
  321.     if (STREQ(field[3], "-"))
  322.         ct->dir = NULL;
  323.     else if (STREQ(field[3], "@")) {
  324.         if (defarch == NULL)
  325.             fail("@ in control file but no -a", "");
  326.         ct->dir = defarch;
  327.     } else {
  328.         ct->dir = strsave(field[3]);
  329.         checkdir(ct->dir);
  330.     }
  331.  
  332.     /* put it where it belongs */
  333.     if (STREQ(ct->groups, "/expired/"))
  334.         holdover = ct;
  335.     else if (STREQ(ct->groups, "/bounds/"))
  336.         bounds = ct;
  337.     else {
  338.         ct->next = NULL;
  339.         if (lastctl == NULL)
  340.             ctls = ct;
  341.         else
  342.             lastctl->next = ct;
  343.         lastctl = ct;
  344.     }
  345. }
  346.  
  347. /*
  348.  - prime - prime control lists from active file
  349.  */
  350. void
  351. prime(afile)
  352. char *afile;
  353. {
  354.     register char *line;
  355.     register FILE *af;
  356.     register struct ctl *ct;
  357. #    define    NFACT    4
  358.     char *field[NFACT];
  359.     int nf;
  360.     register int hash;
  361.  
  362.     af = eufopen(afile, "r");
  363.     while ((line = fgetms(af)) != NULL) {
  364.         nf = split(line, field, NFACT, " \t");
  365.         if (nf != NFACT)
  366.             fail("bad active-file line for `%s'", field[0]);
  367.         ct = (struct ctl *)malloc(sizeof(struct ctl));
  368.         if (ct == NULL)
  369.             fail("out of memory at newsgroup `%s'", field[0]);
  370.         ct->groups = strsave(field[0]);
  371.         ct->ismod = (strchr(field[3], 'm') != NULL) ? MOD : UNMOD;
  372.         fillin(ct);
  373.         hash = strlen(field[0]);
  374.         if (hash > NHASH-1)
  375.             hash = NHASH-1;
  376.         ct->next = ngs[hash];
  377.         ngs[hash] = ct;
  378.         free(line);
  379.     }
  380.     (void) fclose(af);
  381. }
  382.  
  383. /*
  384.  - fillin - fill in a ctl struct for a newsgroup from the control-file list
  385.  */
  386. void
  387. fillin(ct)
  388. register struct ctl *ct;
  389. {
  390.     register struct ctl *cscan;
  391.     char grump[100];
  392.  
  393.     for (cscan = ctls; cscan != NULL; cscan = cscan->next)
  394.         if (ngmatch(cscan->groups, ct->groups) &&
  395.                 (cscan->ismod == ct->ismod ||
  396.                         cscan->ismod == EITHER)) {
  397.             ct->retain = cscan->retain;
  398.             ct->normal = cscan->normal;
  399.             ct->purge = cscan->purge;
  400.             ct->dir = cscan->dir;
  401.             return;
  402.         }
  403.  
  404.     /* oooooops... */
  405.     sprintf(grump, "group `%%s' (%smoderated) not covered by control file",
  406.                     (ct->ismod == MOD) ? "" : "un");
  407.     fail(grump, ct->groups);
  408. }
  409.  
  410. /*
  411.  - doit - file manipulation and master control
  412.  */
  413. void
  414. doit()
  415. {
  416.     register int old;
  417.     register FILE *new;
  418.     char *line;
  419.     long here;
  420.     register char *nameend;
  421.     datum lhs;
  422.     datum rhs;
  423.     register int ret;
  424.  
  425.     cd(ctlfile((char *)NULL));
  426.     old = open("history", 0);
  427.     if (old < 0)
  428.         fail("cannot open `%s'", "history");
  429.     (void) unlink("history.n");
  430.     (void) unlink("history.n.dir");
  431.     (void) unlink("history.n.pag");
  432.     if (spacetight)
  433.         (void) unlink("history.o");
  434.     new = eufopen("history.n", "w");
  435.     (void) fclose(eufopen("history.n.dir", "w"));
  436.     (void) fclose(eufopen("history.n.pag", "w"));
  437.     if (dbminit("history.n") < 0)
  438.         fail("dbminit(history.n) failed", "");
  439.  
  440.     cd(artfile((char *)NULL));
  441.     while ((line = readline(old)) != NULL) {
  442.         line = doline(line);
  443.         if (line != NULL) {
  444.             /* extract the message-id */
  445.             nameend = strchr(line, '\t');
  446.             if (nameend == NULL) {
  447.                 errno = 0;
  448.                 fail("bad return from doline(): `%.75s'", line);
  449.             }
  450.  
  451.             /* make the DBM entry */
  452.             *nameend = '\0';
  453.             lhs.dptr = line;
  454.             lhs.dsize = strlen(line)+1;
  455.             here = ftell(new);
  456.             rhs.dptr = (char *)&here;
  457.             rhs.dsize = sizeof(here);
  458.             errno = 0;
  459.             ret = store(lhs, rhs);
  460.             if (ret < 0)
  461.                 fail("dbm failure on `%s'", line);
  462.             *nameend = '\t';
  463.  
  464.             /* and the history entry */
  465.             fputs(line, new);
  466.             putc('\n', new);
  467.             free(line);
  468.         }
  469.     }
  470.     /* side effect of readline() == NULL:  newslock() */
  471.  
  472.     (void) close(old);
  473.     eufclose(new, "history.n");
  474.  
  475.     if (testing)
  476.         return;
  477.     cd(ctlfile((char *)NULL));
  478.     (void) unlink("history.o");
  479.     if (link("history", "history.o") < 0)
  480.         fail("can't move history", "");
  481.     if (unlink("history") < 0)
  482.         fail("can't finish moving history", "");
  483.     if (link("history.n", "history") < 0)
  484.         fail("disaster -- can't reinstate history!", "");
  485.     if (unlink("history.n") < 0)
  486.         fail("disaster -- can't unlink history.n!", "");
  487.     if (unlink("history.dir") < 0)
  488.         fail("disaster -- can't unlink history.dir!", "");
  489.     if (unlink("history.pag") < 0)
  490.         fail("disaster -- can't unlink history.pag!", "");
  491.     if (link("history.n.dir", "history.dir") < 0)
  492.         fail("disaster -- can't reinstate history.dir!", "");
  493.     if (link("history.n.pag", "history.pag") < 0)
  494.         fail("disaster -- can't reinstate history.pag!", "");
  495.     if (unlink("history.n.dir") < 0)
  496.         fail("disaster -- can't unlink history.n.dir!", "");
  497.     if (unlink("history.n.pag") < 0)
  498.         fail("disaster -- can't unlink history.n.pag!", "");
  499. }
  500.  
  501. /*
  502.  - doline - handle one history line, modifying it if appropriate
  503.  */
  504. char *                /* new (malloced) line; NULL means none */
  505. doline(line)
  506. char *line;            /* malloced; freed here */
  507. {
  508.     char *work;
  509. #    define    NF    3
  510.     char *field[NF];    /* fields in line */
  511.     register int nf;
  512. #    define    NSF    10
  513.     char *subfield[NSF];    /* subfields in middle field */
  514.     register int nsf;
  515.     register time_t recdate;
  516.     register time_t expdate;
  517.     char expbuf[25];        /* plenty for decimal time_t */
  518.     int wasreal;
  519.  
  520.     if (expdebug) {
  521.         fputs("\ndoline `", stderr);
  522.         fputs(line, stderr);
  523.         fputs("'\n", stderr);
  524.     }
  525.  
  526.     /* pull the incoming line apart */
  527.     work = strsave(line);
  528.     nf = split(work, field, NF, "\t");
  529.     if (nf != 3 && nf != 2) {
  530.         free(work);
  531.         errno = 0;
  532.         warning("wrong number of fields in `%.40s...'", line);
  533.         return(line);    /* leaving the line in the new history file */
  534.     }
  535.     if (nf == 2)
  536.         field[2] = NULL;
  537.     nsf = split(field[1], subfield, NSF, subsep);
  538.  
  539.     /* sort out the dates */
  540.     if (nsf < 2 || STREQ(subfield[1], "-") || STREQ(subfield[1], ""))
  541.         expdate = NODATE;
  542.     else {
  543.         expdate = readdate(subfield[1]);
  544.         if (expdate == NODATE) {
  545.             errno = 0;
  546.             warning("bad expiry date in `%.40s...',", line);
  547.             warning(" specifically, `%s' -- ignored", subfield[1]);
  548.         }
  549.     }
  550.     recdate = readdate(subfield[0]);
  551.     if (recdate == NODATE) {
  552.         free(work);
  553.         errno = 0;
  554.         warning("bad arrival date in `%.40s...' -- fix by hand", line);
  555.         return(line);
  556.     }
  557.     free(line);
  558.     if (expdebug)
  559.         fprintf(stderr, "rec %ld, expire %ld\n", (long)recdate,
  560.                                 (long)expdate);
  561.  
  562.     /* deal with it */
  563.     if (nf > 2 && STREQ(field[2], "/"))    /* old C news cancellation */
  564.         field[2] = NULL;
  565.     else if (nf > 2 && STREQ(field[2], "cancelled"))    /* B 2.11 */
  566.         field[2] = NULL;
  567.     wasreal = (field[2] != NULL);
  568.     field[2] = doarticle(field[2], recdate, expdate, field[0]);
  569.     if (wasreal) {
  570.         if (field[2] == NULL)
  571.             ngone++;
  572.         else
  573.             nkept++;
  574.     }
  575.  
  576.     /* construct new line */
  577.     if (field[2] != NULL || (holdover != NULL &&
  578.                 !shouldgo(recdate, NODATE, holdover))) {
  579.         if (expdate != NODATE) {
  580.             sprintf(expbuf, "%ld", (long)expdate);
  581.             subfield[1] = expbuf;
  582.         } else
  583.             subfield[1] = "-";
  584.         field[1] = strvsave(subfield, nsf, *subsep);
  585.         line = strvsave(field, 3, '\t');
  586.         free(field[1]);
  587.         if (expdebug) {
  588.             fputs("new line `", stderr);
  589.             fputs(line, stderr);
  590.             fputs("'\n", stderr);
  591.         }
  592.         if (field[2] == NULL)
  593.             nresid++;
  594.     } else
  595.         line = NULL;
  596.  
  597.     free(work);
  598.     return(line);
  599. }
  600.  
  601. /*
  602.  - readdate - turn a date into internal form
  603.  */
  604. time_t
  605. readdate(text)
  606. char *text;
  607. {
  608.     time_t ret;
  609.  
  610.     if (strspn(text, "0123456789") == strlen(text))
  611.         ret = atol(text);
  612.     else
  613.         ret = getdate(text, &ftnow);
  614.     if (ret == -1)
  615.         ret = NODATE;
  616.  
  617.     return(ret);
  618. }
  619.  
  620. /*
  621.  - doarticle - possibly expire an article
  622.  *
  623.  * Re-uses the space of its first argument.
  624.  */
  625. char *                /* new name list, in space of old, or NULL */
  626. doarticle(oldnames, recdate, expdate, msgid)
  627. char *oldnames;            /* may be destroyed */
  628. time_t recdate;
  629. time_t expdate;
  630. char *msgid;            /* for printstuff() */
  631. {
  632.     register char *src;
  633.     register char *dst;
  634.     register char *name;
  635.     register char *dir;
  636.     register char *p;
  637.     register char srcc;
  638.     register int nleft;
  639.     register int nexpired;
  640. #    define    NDELIM    " ,"
  641.  
  642.     if (oldnames == NULL)
  643.         return(NULL);
  644.  
  645.     src = oldnames;
  646.     dst = oldnames;
  647.     nleft = 0;
  648.     nexpired = 0;
  649.     for (;;) {
  650.         src += strspn(src, NDELIM);
  651.         name = src;
  652.         src += strcspn(src, NDELIM);
  653.         srcc = *src;
  654.         *src = '\0';
  655.         if (*name == '\0')
  656.             break;        /* NOTE BREAK OUT */
  657.         if (expdebug)
  658.             fprintf(stderr, "name `%s'\n", name);
  659.  
  660.         dir = whereexpire(recdate, expdate, name);
  661.         if (dir != dont && !(leaders && nleft == 0 && srcc != '\0')) {
  662.             if (expdebug)
  663.                 fprintf(stderr, "expire into `%s'\n",
  664.                     (dir == NULL) ? "(null)" : dir);
  665.             for (p = strchr(name, '.'); p != NULL;
  666.                             p = strchr(p+1, '.'))
  667.                 *p = '/';
  668.             expire(name, dir);
  669.             if (dir != NULL && printexpiring)
  670.                 printstuff(msgid, name, recdate);
  671.             nexpired++;
  672.         } else {
  673.             if (dst != oldnames)
  674.                 *dst++ = ' ';
  675.             while (*name != '\0')
  676.                 *dst++ = *name++;
  677.             nleft++;
  678.         }
  679.         *src = srcc;
  680.     }
  681.  
  682.     if (nleft == 0)
  683.         return(NULL);
  684.     *dst++ = '\0';
  685.     if (leaders && nleft == 1 && nexpired > 0)    /* aging leader */
  686.         return(doarticle(oldnames, recdate, expdate, msgid));
  687.     return(oldnames);
  688. }
  689.  
  690. /*
  691.  - whereexpire - where should this name expire to, and should it?
  692.  *
  693.  * The "dont" variable's address is used as the don't-expire return value,
  694.  * since NULL means "to nowhere".
  695.  */
  696. char *                /* archive directory, NULL, or dont */
  697. whereexpire(recdate, expdate, name)
  698. time_t recdate;
  699. time_t expdate;
  700. char *name;
  701. {
  702.     register char *group;
  703.     register char *slash;
  704.     register struct ctl *ct;
  705.     register int hash;
  706.  
  707.     group = name;
  708.     slash = strchr(group, '/');
  709.     if (slash == NULL) {
  710.         errno = 0;
  711.         fail("no slash in article path `%s'", name);
  712.     } else
  713.         *slash = '\0';
  714.     if (strchr(slash+1, '/') != NULL) {
  715.         *slash = '/';
  716.         errno = 0;
  717.         fail("multiple slashes in article path `%s'", name);
  718.     }
  719.  
  720.     /* find applicable expiry-control struct (make it if necessary) */
  721.     hash = strlen(group);
  722.     if (hash > NHASH-1)
  723.         hash = NHASH-1;
  724.     for (ct = ngs[hash]; ct != NULL && !STREQ(ct->groups, group);
  725.                                 ct = ct->next)
  726.         continue;
  727.     if (ct == NULL) {    /* oops, there wasn't one */
  728.         if (expdebug)
  729.             fprintf(stderr, "new group `%s'\n", group);
  730.         ct = (struct ctl *)malloc(sizeof(struct ctl));
  731.         if (ct == NULL)
  732.             fail("out of memory for newsgroup `%s'", group);
  733.         ct->groups = strsave(group);
  734.         ct->ismod = UNMOD;    /* unknown -- treat it as mundane */
  735.         fillin(ct);
  736.         ct->next = ngs[hash];
  737.         ngs[hash] = ct;
  738.     }
  739.     *slash = '/';
  740.  
  741.     /* and decide */
  742.     if (shouldgo(recdate, expdate, ct))
  743.         return(ct->dir);
  744.     else
  745.         return(dont);
  746. }
  747.  
  748. /*
  749.  - shouldgo - should article with these dates expire now?
  750.  */
  751. int                /* predicate */
  752. shouldgo(recdate, expdate, ct)
  753. time_t recdate;
  754. time_t expdate;
  755. register struct ctl *ct;
  756. {
  757.     if (recdate >= ct->retain)    /* within retention period */
  758.         return(0);
  759.     if (recdate <= ct->purge)    /* past purge date */
  760.         return(1);
  761.     if (expdate != NODATE) {
  762.         if (now >= expdate)    /* past its explicit date */
  763.             return(1);
  764.         else
  765.             return(0);
  766.     } else {
  767.         if (recdate < ct->normal)    /* past default date */
  768.             return(1);
  769.         else
  770.             return(0);
  771.     }
  772.     /* NOTREACHED */
  773. }
  774.  
  775. /*
  776.  - expire - expire an article
  777.  */
  778. void
  779. expire(name, dir)
  780. char *name;
  781. char *dir;
  782. {
  783.     register char *old;
  784.     register char *new;
  785.     struct stat stbuf;
  786.  
  787.     if (testing) {
  788.         if (dir != NULL)
  789.             fprintf(stderr, "copy %s %s ; ", name, dir);
  790.         fprintf(stderr, "remove %s\n", name);
  791.         return;
  792.     }
  793.  
  794.     old = strsave(artfile(name));
  795.     if (dir != NULL) {
  796.         if (*dir == '=') {
  797.             errno = 0;
  798.             new = strrchr(name, '/');
  799.             if (new == NULL)
  800.                 fail("no slash in `%s'", name);
  801.             new++;
  802.             new = str3save(dir+1, "/", new);
  803.         } else
  804.             new = str3save(dir, "/", name);
  805.         /* cp() usually succeeds, so try it before getting fancy */
  806.         if (cp(old, new) < 0) {
  807.             if (stat(old, &stbuf) < 0) {
  808.                 nmissing++;
  809.                 free(old);
  810.                 free(new);
  811.                 return;        /* nonexistent */
  812.             }
  813.             if (*dir != '=')
  814.                 mkparents(name, dir);
  815.             if (cp(old, new) < 0) {
  816.                 warning("can't archive `%s'", name);
  817.                 free(old);
  818.                 free(new);
  819.                 return;        /* without removing it */
  820.             }
  821.         }
  822.         free(new);
  823.         narched++;
  824.     }
  825.     if (unlink(old) < 0) {
  826.         if (errno != ENOENT)
  827.             warning("can't remove `%s'", name);
  828.         else
  829.             nmissing++;
  830.     } else if (dir == NULL)
  831.         njunked++;
  832.     free(old);
  833. }
  834.  
  835. /*
  836.  - cp - try to copy an article
  837.  */
  838. int                /* 0 success, -1 failure */
  839. cp(src, dst)
  840. char *src;            /* absolute pathnames */
  841. char *dst;
  842. {
  843.     register int ret;
  844.     register int count;
  845.     register int in, out;
  846.     register int firstblock = 1;
  847.  
  848.     in = open(src, 0);
  849.     if (in < 0)
  850.         return(-1);
  851.     out = creat(dst, 0666);
  852.     if (out < 0) {
  853.         (void) close(in);
  854.         return(-1);
  855.     }
  856.  
  857.     while ((count = read(in, abuf, sizeof(abuf))) > 0) {
  858.         ret = write(out, abuf, count);
  859.         if (ret != count)
  860.             fail("write error in copying `%s'", src);
  861.         if (firstblock) {
  862.             getsubj(abuf, count);
  863.             firstblock = 0;
  864.         }
  865.     }
  866.     if (count < 0)
  867.         fail("read error in copying `%s'", src);
  868.  
  869.     (void) close(in);
  870.     euclose(out, dst);
  871.     return(0);
  872. }
  873.  
  874. /*
  875.  - getsubj - try to find the Subject: line in a buffer
  876.  *
  877.  * Result goes in "subject", and is never empty.  Tabs become spaces,
  878.  * since they are the output delimiters.
  879.  */
  880. void
  881. getsubj(buf, bsize)
  882. char *buf;
  883. int bsize;
  884. {
  885.     register char *scan;
  886.     register char *limit;
  887.     register int len;
  888.     register int clipped;
  889.     static char sline[] = "Subject:";
  890.  
  891.     len = strlen(sline);
  892.     limit = buf + bsize - len;
  893.     for (scan = buf; scan < limit; scan++)
  894.         if (STREQN(scan, sline, len) &&
  895.                 (scan == buf || *(scan-1) == '\n')) {
  896.             scan += len;
  897.             for (limit = scan; limit < buf+bsize; limit++)
  898.                 if (*limit == '\n')
  899.                     break;
  900.             while (scan < limit && isspace(*scan))
  901.                 scan++;
  902.             len = limit-scan;
  903.             clipped = 0;
  904.             if (len > sizeof(subject)-1) {
  905.                 len = sizeof(subject) - 1 - strlen("...");
  906.                 clipped = 1;
  907.             }
  908.             if (len > 0) {
  909.                 (void) strncpy(subject, scan, len);
  910.                 subject[len] = '\0';
  911.             } else
  912.                 (void) strcpy(subject, "???");
  913.             if (clipped)
  914.                 (void) strcat(subject, "...");
  915.             for (scan = strchr(subject, '\t'); scan != NULL;
  916.                     scan = strchr(scan+1, '\t'))
  917.                 *scan = ' ';
  918.             return;
  919.         } else if (*scan == '\n' && scan+1 < limit && *(scan+1) == '\n')
  920.             break;        /* empty line terminates header */
  921.  
  922.     /* didn't find one -- fill in *something* */
  923.     (void) strcpy(subject, "???");
  924. }
  925.  
  926. /*
  927.  - mkparents - try to make directories for archiving an article
  928.  *
  929.  * Assumes it can mess with first argument if it puts it all back at the end.
  930.  */
  931. void
  932. mkparents(art, dir)
  933. char *art;            /* name relative to dir */
  934. char *dir;
  935. {
  936.     register char *cmd;
  937.     register char *ocmd;
  938.     register char *p;
  939.  
  940.     p = strchr(art, '/');
  941.     cmd = str3save(binfile((char *)NULL), "/expire/mkadir ", dir);
  942.     while (p != NULL) {
  943.         *p = '\0';
  944.         ocmd = cmd;
  945.         cmd = str3save(ocmd, " ", art);
  946.         free(ocmd);
  947.         *p = '/';
  948.         p = strchr(p+1, '/');
  949.     }
  950.     (void) system(cmd);
  951.     free(cmd);
  952. }
  953.  
  954. char *months[12] = {
  955.     "Jan",
  956.     "Feb",
  957.     "Mar",
  958.     "Apr",
  959.     "May",
  960.     "Jun",
  961.     "Jul",
  962.     "Aug",
  963.     "Sep",
  964.     "Oct",
  965.     "Nov",
  966.     "Dec",
  967. };
  968.  
  969. /*
  970.  - printstuff - print information about an expiring article
  971.  */
  972. void
  973. printstuff(msgid, name, recdate)
  974. char *msgid;
  975. char *name;
  976. time_t recdate;
  977. {
  978.     struct tm *gmt;
  979.  
  980.     gmt = gmtime(&recdate);
  981.     printf("%s\t%s\t%d-%s-%d\t%s\n", name, msgid, gmt->tm_mday,
  982.             months[gmt->tm_mon], gmt->tm_year+1900, subject);
  983. }
  984.  
  985. /*
  986.  - split - divide a line into fields, like awk split()
  987.  */
  988. int                /* number of fields */
  989. split(line, fields, nfmax, sep)
  990. char *line;
  991. char *fields[];
  992. int nfmax;
  993. char *sep;
  994. {
  995.     register int i;
  996.     register char *p;
  997.  
  998.     i = 0;
  999.     for (p = strtok(line, sep); p != NULL; p = strtok((char *)NULL, sep)) {
  1000.         if (i < nfmax)
  1001.             fields[i] = p;
  1002.         i++;
  1003.     }
  1004.  
  1005.     return(i);
  1006. }
  1007.  
  1008. /*
  1009.  - eufopen - fopen, with fail if doesn't succeed
  1010.  */
  1011. FILE *
  1012. eufopen(name, mode)
  1013. char *name;
  1014. char *mode;
  1015. {
  1016.     FILE *f;
  1017.     static char grump[50] = "can't open `%s' for `";
  1018.  
  1019.     f = fopen(name, mode);
  1020.     if (f == NULL) {
  1021.         (void) strcat(grump, mode);
  1022.         (void) strcat(grump, "'");
  1023.         fail(grump, name);
  1024.     }
  1025.     return(f);
  1026. }
  1027.  
  1028. /*
  1029.  - eufclose - fclose with failure checking
  1030.  */
  1031. void
  1032. eufclose(f, name)
  1033. FILE *f;
  1034. char *name;
  1035. {
  1036.     if (nfclose(f) == EOF)
  1037.         fail("error in closing file `%s'", name);
  1038. }
  1039.  
  1040. /*
  1041.  - euclose - close with failure checking
  1042.  */
  1043. void
  1044. euclose(f, name)
  1045. int f;
  1046. char *name;
  1047. {
  1048.     if (fsync(f) < 0 || close(f) < 0)
  1049.         fail("error in closing file `%s'", name);
  1050. }
  1051.  
  1052. /*
  1053.  - checkdir - check that a directory is real, writeable, and full pathname
  1054.  */
  1055. void                /* fail() if not */
  1056. checkdir(dir)
  1057. char *dir;
  1058. {
  1059.     struct stat stbuf;
  1060. #    define    GRUMP(a,b)    {if (checkonly) {warning(a, b);return;} else fail(a, b);}
  1061.  
  1062.     if (*dir == '=')    /* disregard leading '=' */
  1063.         dir++;
  1064.     errno = 0;
  1065.     if (stat(dir, &stbuf) < 0 || (stbuf.st_mode&S_IFMT) != S_IFDIR)
  1066.         GRUMP("`%s' is not a directory", dir);
  1067.     if (access(dir, 02) < 0)
  1068.         GRUMP("directory `%s' not writeable", dir);
  1069.     if (dir[0] != '/')
  1070.         GRUMP("directory `%s' not an absolute pathname", dir);
  1071. }
  1072.  
  1073. /*
  1074.  - back - get a date n days back, with overflow check
  1075.  *
  1076.  * Requires that "now" be set first.
  1077.  */
  1078. time_t
  1079. back(ndays)
  1080. double ndays;
  1081. {
  1082.     if (ndays > now / DAY)    /* past beginning of time */
  1083.         return((time_t)0);
  1084.     return(now - ndays*DAY);
  1085. }
  1086.  
  1087. /*
  1088.  - printlists - print control lists for debugging
  1089.  */
  1090. void
  1091. printlists()
  1092. {
  1093.     register int i;
  1094.     register struct ctl *ct;
  1095.  
  1096.     fprintf(stderr, "control file:\n");
  1097.     for (ct = ctls; ct != NULL; ct = ct->next)
  1098.         pctl(ct);
  1099.     fprintf(stderr, "\n");
  1100.  
  1101.     for (i = 0; i < NHASH; i++)
  1102.         if (ngs[i] != NULL) {
  1103.             fprintf(stderr, "list %d:\n", i);
  1104.             for (ct = ngs[i]; ct != NULL; ct = ct->next)
  1105.                 pctl(ct);
  1106.         }
  1107.     fprintf(stderr, "\n");
  1108. }
  1109.  
  1110. /*
  1111.  - pctl - print one control-list entry
  1112.  */
  1113. void
  1114. pctl(ct)
  1115. register struct ctl *ct;
  1116. {
  1117. #    define    DAYS(x)    ((now-(x))/DAY)
  1118.  
  1119.     fprintf(stderr, "%s(%c) %.2f-%.2f-%.2f %s\n", ct->groups, ct->ismod,
  1120.             DAYS(ct->retain), DAYS(ct->normal), DAYS(ct->purge),
  1121.             (ct->dir == NULL) ? "(null)" : ct->dir);
  1122. }
  1123.  
  1124. /*
  1125.  - unprivileged - no-op needed to keep the pathname stuff happy
  1126.  */
  1127. void
  1128. unprivileged()
  1129. {
  1130. }
  1131.  
  1132. /*
  1133.  - fail - call errunlock, possibly after cleanup
  1134.  */
  1135. void
  1136. fail(s1, s2)
  1137. char *s1;
  1138. char *s2;
  1139. {
  1140.     if (spacetight) {
  1141.         cd(ctlfile((char *)NULL));
  1142.         (void) unlink("history.n");
  1143.         (void) unlink("history.n.dir");
  1144.         (void) unlink("history.n.pag");
  1145.     }
  1146.     errunlock(s1, s2);
  1147.     /* NOTREACHED */
  1148. }
  1149.  
  1150. /*
  1151.  - readline - read history line (sans newline), with locking when we hit EOF
  1152.  *
  1153.  * Minor flaw:  will lose a last line which lacks a newline.
  1154.  */
  1155. char *                /* NULL is EOF */
  1156. readline(fd)
  1157. int fd;                /* Note descriptor, not FILE *. */
  1158. {
  1159.     register char *line;
  1160.     register int nline;
  1161.     register char *p;
  1162.     register int c;
  1163.     register int n;
  1164.     extern void refill();
  1165.  
  1166.     nline = 100;        /* reasonable starter */
  1167.     line = malloc(nline);
  1168.     if (line == NULL)
  1169.         fail("out of space when reading history", "");
  1170.     p = line;
  1171.  
  1172.     for (;;) {
  1173.         if (rlnleft <= 0) {
  1174.             refill(fd);
  1175.             if (rlnleft <= 0)    /* refill gave up. */
  1176.                 return(NULL);
  1177.         }
  1178.         c = *rest++;
  1179.         rlnleft--;
  1180.  
  1181.         if (c == '\n') {
  1182.             *p++ = '\0';
  1183.             return(line);
  1184.         }
  1185.         if (p - line >= nline - 1) {
  1186.             nline = (nline * 3) / 2;
  1187.             n = p - line;
  1188.             line = realloc(line, nline);
  1189.             if (line == NULL)
  1190.                 fail("out of memory in readline", "");
  1191.             p = line + n;
  1192.         }
  1193.         *p++ = c;
  1194.     }
  1195.     /* NOTREACHED */
  1196. }
  1197.  
  1198. /*
  1199.  - refill - refill readline's buffer, with locking on EOF
  1200.  */
  1201. void
  1202. refill(fd)
  1203. int fd;
  1204. {
  1205.     register int ret;
  1206.  
  1207.     /* Just in case... */
  1208.     if (rlnleft > 0)
  1209.         return;
  1210.  
  1211.     /* Try ordinary read. */
  1212.     ret = read(fd, rlbuf, (int)sizeof(rlbuf));
  1213.     if (ret < 0)
  1214.         fail("read error in history", "");
  1215.     if (ret > 0) {
  1216.         rlnleft = ret;
  1217.         rest = rlbuf;
  1218.         return;
  1219.     }
  1220.  
  1221.     /* EOF. */
  1222.     if (nlocked)
  1223.         return;        /* We're really done. */
  1224.  
  1225.     /* EOF but we haven't locked yet.  Lock and try again. */
  1226.     (void) signal(SIGINT, (sigarg_t)SIG_IGN);
  1227.     (void) signal(SIGQUIT, (sigarg_t)SIG_IGN);
  1228.     (void) signal(SIGHUP, (sigarg_t)SIG_IGN);
  1229.     (void) signal(SIGTERM, (sigarg_t)SIG_IGN);
  1230.     newslock();
  1231.     nlocked = 1;
  1232.     refill(fd);
  1233. }
  1234.  
  1235. /*
  1236.  - strvsave - sort of like strsave, but for a vector of strings
  1237.  */
  1238. char *
  1239. strvsave(v, nv, delim)
  1240. char **v;
  1241. int nv;
  1242. char delim;
  1243. {
  1244.     register char **p;
  1245.     register int i;
  1246.     register char *result;
  1247.     register char *rp;
  1248.     register int len;
  1249.  
  1250.     if (nv <= 0)
  1251.         return(strsave(""));
  1252.  
  1253.     len = 0;
  1254.     for (i = nv, p = v; i > 0; i--, p++)
  1255.         if (*p != NULL)
  1256.             len += strlen(*p) + 1;
  1257.     result = malloc(len);
  1258.     if (result == NULL)
  1259.         fail("out of memory in strvsave", "");
  1260.  
  1261.     rp = result;
  1262.     for (i = nv, p = v; i > 0; i--, p++)
  1263.         if (*p != NULL) {
  1264.             (void) strcpy(rp, *p);
  1265.             rp += strlen(rp);
  1266.             *rp++ = delim;
  1267.         }
  1268.     rp--;
  1269.     *rp = '\0';
  1270.  
  1271.     return(result);
  1272. }
  1273.